Skip to content

refactor(table): carry the root caret offset in a WeakMap, not a DOM stamp - #359

Merged
mtskf merged 4 commits into
mainfrom
chore/table-widget-root-weakmap
Aug 14, 2026
Merged

refactor(table): carry the root caret offset in a WeakMap, not a DOM stamp#359
mtskf merged 4 commits into
mainfrom
chore/table-widget-root-weakmap

Conversation

@mtskf

@mtskf mtskf commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Summary

Converges TableBlockWidget's ROOT position channel on the same module-private WeakMap the image widget adopted in #356, so both block widgets carry their block start the same way. The per-CELL stamps deliberately stay on the DOM.

Changes

  • table-widget.ts: new module-private blockStart: WeakMap<HTMLElement, number> (alongside the existing pendingDrag, same rationale), written in toDOM beside the listener and re-pointed in updateDOM. The click listener's caret fallback now reads that map instead of stampedOffset(root, "data-doc-from"). data-doc-from is still written for DOM inspection but never read back.
  • The old third arm of the fallback chain — the toDOM-time this.docFrom closure — is gone as a routine path. It was the one link that could reveal a different block after a positional shift, since updateDOM cannot re-bind the listener. The remaining blockStart miss is unreachable by construction and logs an invariant-violation breadcrumb (identifying the widget by slice) before falling back, mirroring image-widget.ts.
  • Per-cell data-cell-from / data-cell-to stay on the DOM and keep going through stampedOffset: cellPointAt resolves an arbitrary descendant under the pointer, so no closure knows which cell was clicked.
  • Comments updated where they described the old chain (file header, click listener, updateDOM re-stamp).

Behaviour is unchanged in every reachable state — nothing outside Quoll writes that attribute — so this removes an unrepresentable failure state rather than fixing an incident.

Related

Test Plan

  • test/webview/table/cm-table-widget.test.ts: the malformed-root-stamp test is transposed to the new channel — now an it.each asserting a "abc" and a well-formed-but-wrong "999" data-doc-from written onto the root are both ignored (the "999" row is what a format gate alone would have missed).
  • Non-vacuity observed: deleting blockStart.set(dom, …) in updateDOM reddens "re-stamps offsets on updateDOM so a click after a shift uses the new base" (anchor 0 instead of 5); reverting the fallback to stampedOffset(root, …) reddens the "999" row; reverting it to a bare Number(root.dataset.docFrom) reddens both rows.
  • pnpm compile, pnpm lint, pnpm test:unit (4974 passed), pnpm test:browser (36 passed).
  • pnpm build + pnpm package (vsix audit clean) + code --install-extension --force.
  • Manual smoke: click a table's margin, then type in a paragraph above the table and click the margin again — the reveal opens on the same table both times.

mtskf added 4 commits August 14, 2026 14:00
…stamp

TableBlockWidget's margin-click caret fallback read the block start back out
of `data-doc-from`. That channel existed only because `updateDOM` reuses the
root element across widget instances and cannot re-bind the click listener,
whose captured `this` stays the old instance — the same problem the image
widget had until #356.

The channel does not have to be the DOM. A module-private
`WeakMap<HTMLElement, number>` has the same per-element, updateDOM-writable
lifetime — the pattern `pendingDrag` already uses two declarations above —
and keeps the value a number end to end, so there is no malformed state left
to gate. Both block widgets now share one root channel.

The per-CELL stamps stay on the DOM: `cellPointAt` resolves an arbitrary
descendant under the pointer and no closure knows which cell was clicked, so
`stampedOffset` keeps gating those reads. `data-doc-from` is still written for
DOM inspection but never read back.

This also removes the chain's weakest arm. The old third arm — the toDOM-time
`this.docFrom` closure, reachable once both stamps failed the gate — could
reveal a DIFFERENT block after a positional shift. The block start now always
comes from a channel `updateDOM` re-points, so the "a degraded caret still
reveals this table" guarantee holds unconditionally; the remaining fallback is
unreachable by construction and logs if it is ever hit.

Behaviour is unchanged in every reachable state — nothing outside Quoll writes
that attribute — so this buys an unrepresentable failure state, not a fix.
… comment

- Add a test that pins the blockStart WeakMap write in toDOM: existing
  margin-click tests couldn't kill that deletion because the miss
  fallback (widget.docFrom) dispatches the same anchor value in every
  fixture here. The new test asserts the miss console.error breadcrumb
  is NOT called, which the deletion does trigger.
- Correct the kill-matrix comment above the data-doc-from it.each block:
  it previously claimed both rows redden on any attribute-read revert,
  but the gated pre-refactor read (stampedOffset ?? this.docFrom) only
  reddens the well-formed-but-wrong row — the malformed row falls
  through that gate to the same value the test expects. Verified both
  revert flavours by temporarily restoring each expression and running
  the suite.
…tating it

Comment-only pass over this PR's diff. No executable code, test rows, or
assertions changed.

The "checkSelection only rejects range.to > doc.length, so NaN lands a
silently broken selection" argument was being re-derived three times here.
It already lives on `stampedOffset` in cell-point.ts, whose docblock names
this file's caret path as a consumer, so `blockStart` and the click listener
now point there and keep only their own local claims: why the block start
needs no gate, why the cell offset must stay on the DOM and go through one,
and why the "degraded caret still reveals this table" guarantee is now
unconditional.

The two test comments lose their length without losing a fact — both kill
matrices and the non-vacuity argument survive verbatim in substance — and
drop two self-references to notes that exist nowhere in the repo.
- Fix the 'both rows are needed' overstatement on the data-doc-from
  kill-matrix comment: '999' alone kills both reverts; 'abc' is
  redundant for kill power there but isolates a separate NaN-silent
  failure mode against the bare read.
- Narrow the blockStart margin-click comment's universal claim: only
  fixtures that click right after toDOM share docFrom between the
  WeakMap entry and the fallback closure. The updateDOM re-stamp
  fixture stays green for an unrelated reason (its own blockStart.set
  write re-fills the entry).
@mtskf
mtskf merged commit 142d9dc into main Aug 14, 2026
2 checks passed
@mtskf
mtskf deleted the chore/table-widget-root-weakmap branch August 14, 2026 07:18
mtskf added a commit that referenced this pull request Aug 16, 2026
…umb click (#360)

Every click fixture in cm-image-widget.test.ts builds the widget and clicks
it in the same breath, so the listener's miss fallback (this.docFrom, the
toDOM-time closure) coincides with the WeakMap value: deleting
blockStart.set(root, this.docFrom) from toDOM left the whole suite green.
Assert instead that no miss breadcrumb is logged for a freshly-built widget,
which is the only observable difference. Mirrors the pin PR #359 added on the
table widget, closing the same gap on the image widget that introduced the
pattern.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant